/*
 * Wazuh Vulnerability scanner - Scan Orchestrator
 * Copyright (C) 2015, Wazuh Inc.
 * March 25, 2023.
 *
 * This program is free software; you can redistribute it
 * and/or modify it under the terms of the GNU General Public
 * License (version 2) as published by the FSF - Free Software
 * Foundation.
 */

#ifndef _SCAN_ORCHESTRATOR_HPP
#define _SCAN_ORCHESTRATOR_HPP

#include "factoryOrchestrator.hpp"
#include "flatbuffers/include/syscollector_deltas_generated.h"
#include "flatbuffers/include/syscollector_synchronization_generated.h"
#include "indexerConnector.hpp"
#include "logging_helper.h"
#include "scanContext.hpp"
#include <functional>
#include <memory>
#include <string>
#include <variant>
#include <vector>

constexpr auto INVENTORY_DB_PATH = "queue/vd/inventory";

/**
 * @brief ScanOrchestrator class.
 *
 */
template<typename TScanContext = ScanContext,
         typename TFactoryOrchestrator = FactoryOrchestrator,
         typename TOrchestrationNode = AbstractHandler<std::shared_ptr<TScanContext>>,
         typename TIndexerConnector = IndexerConnector,
         typename TDatabaseFeedManager = DatabaseFeedManager,
         typename TSocketDBWrapper = SocketDBWrapper,
         typename TOSPrimitives = OSPrimitives>
class TScanOrchestrator final : public TOSPrimitives
{
public:
    /**
     * @brief Class constructor.
     *
     * @param indexerConnector Indexer connector.
     * @param databaseFeedManager Database feed manager.
     * @param reportDispatcher Report dispatcher queue to send vulnerability reports.
     * @param mutex Mutex to protect the access to the internal databases.
     */
    // LCOV_EXCL_START
    explicit TScanOrchestrator(std::shared_ptr<TIndexerConnector> indexerConnector,
                               std::shared_ptr<TDatabaseFeedManager> databaseFeedManager,
                               std::shared_ptr<ReportDispatcher> reportDispatcher,
                               std::shared_mutex& mutex)
        : m_mutex {mutex}
    {
        m_inventoryDatabase = std::make_unique<Utils::RocksDBWrapper>(INVENTORY_DB_PATH);
        auto& inventoryDatabase = *m_inventoryDatabase;

        m_osOrchestration = TFactoryOrchestrator::create(
            ScannerType::Os, databaseFeedManager, indexerConnector, inventoryDatabase, reportDispatcher);
        m_packageInsertOrchestration = TFactoryOrchestrator::create(
            ScannerType::PackageInsert, databaseFeedManager, indexerConnector, inventoryDatabase, reportDispatcher);
        m_packageDeleteOrchestration = TFactoryOrchestrator::create(
            ScannerType::PackageDelete, databaseFeedManager, indexerConnector, inventoryDatabase, reportDispatcher);
        m_integrityClearOrchestration = TFactoryOrchestrator::create(
            ScannerType::IntegrityClear, databaseFeedManager, indexerConnector, inventoryDatabase, reportDispatcher);
        m_reScanAllOrchestration = TFactoryOrchestrator::create(ScannerType::ReScanAllAgents,
                                                                databaseFeedManager,
                                                                indexerConnector,
                                                                inventoryDatabase,
                                                                reportDispatcher,
                                                                m_packageInsertOrchestration);
        m_reScanOrchestration = TFactoryOrchestrator::create(ScannerType::ReScanSingleAgent,
                                                             databaseFeedManager,
                                                             indexerConnector,
                                                             inventoryDatabase,
                                                             reportDispatcher,
                                                             m_packageInsertOrchestration);
        m_deleteAgentScanOrchestration = TFactoryOrchestrator::create(
            ScannerType::CleanupAgentData, databaseFeedManager, indexerConnector, inventoryDatabase, reportDispatcher);
        m_cleanUpDataOrchestration = TFactoryOrchestrator::create(ScannerType::CleanupAllData,
                                                                  std::move(databaseFeedManager),
                                                                  std::move(indexerConnector),
                                                                  inventoryDatabase,
                                                                  std::move(reportDispatcher));
        //  m_hotfixDeleteOrchestration = TFactoryOrchestrator::create(ScannerType::HotfixDelete);

        nlohmann::json response; // JSON object to store the response from the database
        std::string managerName; // String to store the retrieved manager name

        // Try to query the database to retrieve the manager name
        try
        {
            // Query the database to get the manager name
            static TSocketDBWrapper socketDBWrapper(WDB_SOCKET);
            socketDBWrapper.query(WazuhDBQueryBuilder::builder().globalSelectCommand("agent-name 000").build(),
                                  response);

            // If the response is not empty, retrieve the manager name
            if (!response.empty())
            {
                managerName = response.front().at("name");
            }
            else
            {
                // If the response is empty, throw an exception
                throw std::runtime_error(
                    "Failed to retrieve manager name. The response from the global database was empty.");
            }
        }
        // Catch any exceptions that occur during the database query
        catch (const std::exception& e)
        {
            // Log a warning indicating the failure to retrieve the manager name
            logWarn(WM_VULNSCAN_LOGTAG, "%s, Using the hostname by fallback.", e.what());

            // Define the maximum size for the hostname
            constexpr auto MAX_HOSTNAME_SIZE = 256;
            char managerNameRaw[MAX_HOSTNAME_SIZE] = {0};

            // Get the hostname and store it in the managerName string
            TOSPrimitives::gethostname(managerNameRaw, MAX_HOSTNAME_SIZE);

            managerName = managerNameRaw;
        }

        // Set the retrieved manager name in the global data object
        GlobalData::instance().managerName(managerName);
    }
    ~TScanOrchestrator() = default;
    // LCOV_EXCL_STOP

    /**
     * @brief Runs orchestrator, decoding and building context.
     *
     * @param data Variant with delta, sync or json message.
     */
    void run(std::variant<const SyscollectorDeltas::Delta*,
                          const SyscollectorSynchronization::SyncMsg*,
                          const nlohmann::json*> data)
    {
        // The scan only reads the content
        std::shared_lock<std::shared_mutex> lock(m_mutex);
        // Decoder, build context with builder pattern.
        auto context = std::make_shared<TScanContext>(data);
        const auto type = context->getType();

        switch (type)
        {
            case ScannerType::PackageInsert: m_packageInsertOrchestration->handleRequest(std::move(context)); break;
            case ScannerType::PackageDelete: m_packageDeleteOrchestration->handleRequest(std::move(context)); break;
            case ScannerType::HotfixInsert: std::cout << "ScanOrchestrator::run::HotfixInserted" << std::endl; break;
            case ScannerType::HotfixDelete: std::cout << "ScanOrchestrator::run::HotfixDeleted" << std::endl; break;
            case ScannerType::Os: m_osOrchestration->handleRequest(std::move(context)); break;
            case ScannerType::IntegrityClear: m_integrityClearOrchestration->handleRequest(std::move(context)); break;
            case ScannerType::CleanupAllData: m_cleanUpDataOrchestration->handleRequest(std::move(context)); break;
            case ScannerType::ReScanAllAgents: m_reScanAllOrchestration->handleRequest(std::move(context)); break;
            case ScannerType::ReScanSingleAgent: m_reScanOrchestration->handleRequest(std::move(context)); break;
            case ScannerType::CleanupAgentData:
                m_deleteAgentScanOrchestration->handleRequest(std::move(context));
                break;
            default: return;
        }

        logDebug2(WM_VULNSCAN_LOGTAG, "Event type: %d processed", type);
    }

private:
    /**
     * @brief Indexer connector.
     *
     */
    std::string m_managerName;
    std::unique_ptr<Utils::RocksDBWrapper> m_inventoryDatabase;
    std::shared_ptr<TOrchestrationNode> m_osOrchestration;
    std::shared_ptr<TOrchestrationNode> m_packageInsertOrchestration;
    std::shared_ptr<TOrchestrationNode> m_packageDeleteOrchestration;
    std::shared_ptr<TOrchestrationNode> m_hotfixInsertOrchestration;
    std::shared_ptr<TOrchestrationNode> m_hotfixDeleteOrchestration;
    std::shared_ptr<TOrchestrationNode> m_integrityClearOrchestration;
    std::shared_ptr<TOrchestrationNode> m_fetchAllFromGlobalDbOrchestration;
    std::shared_ptr<TOrchestrationNode> m_reScanAllOrchestration;
    std::shared_ptr<TOrchestrationNode> m_reScanOrchestration;
    std::shared_ptr<TOrchestrationNode> m_cleanUpDataOrchestration;
    std::shared_ptr<TOrchestrationNode> m_deleteAgentScanOrchestration;
    std::shared_mutex& m_mutex;
};

using ScanOrchestrator = TScanOrchestrator<>;

#endif // _SCAN_ORCHESTRATOR_HPP
